﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BobcatUtility;
using System.Runtime.Remoting.Contexts;
using CNUtility;

namespace CNShapes
{
    /// <summary>
    /// Holds a number of Pages, and represents one drawing session which may 
    /// consist of a number of different drawings.
    /// 
    /// Pages and ActivePage
    /// --------------------
    /// The session holds a collection of pages (Page objects).
    /// One of the pages is the active-page. This is the one that you are currently
    /// drawing on.
    /// 
    /// Singleton
    /// ---------
    /// This class is a singleton, as the whiteboard app has just one session
    /// object which holds the current state of the set of drawings.
    /// </summary>
    [Synchronization]
    public class Session : ContextBoundObject
    {
        #region Events

        /// <summary>
        /// Data raised with events from this class.
        /// </summary>
        public class Args : EventArgs
        {
            public Args(Page page, Shape shape, ActionOrigin actionOrigin)
            {
                Page = page;
                Shape = shape;
                ActionOrigin = actionOrigin;
            }

            public Page Page { get; set; }
            public Shape Shape { get; set; }
            public ActionOrigin ActionOrigin { get; set; }
        }

        /// <summary>
        /// Event raised when a new session is started.
        /// </summary>
        public event EventHandler<Args> NewSessionEvent;

        /// <summary>
        /// Event raised when the active page has changed.
        /// </summary>
        public event EventHandler<Args> ActivePageChangedEvent;

        /// <summary>
        /// Event raised when a page is added to the session.
        /// </summary>
        public event EventHandler<Args> PageAddedEvent;

        /// <summary>
        /// Event raised when a page is removed from the session.
        /// </summary>
        public event EventHandler<Args> PageRemovedEvent;

        /// <summary>
        /// Event raised when a shape on one of the pages we're managing has
        /// finished being edited.
        /// </summary>
        public event EventHandler<Args> ShapeEditCompletedEvent;

        /// <summary>
        /// Event raised when a shape has been added to one of the pages
        /// we are managing.
        /// </summary>
        public event EventHandler<Args> ShapeAddedEvent;

        /// <summary>
        /// Event raised when a shape has been removed from one of the
        /// pages we are managing.
        /// </summary>
        public event EventHandler<Args> ShapeRemovedEvent;

        #endregion

        #region Public properties

        /// <summary>
        /// Property to get and set the session's ID.
        /// </summary>
        public Guid ID
        {
            get { return m_ID; }
        }

        /// <summary>
        /// Property to get the list of pages for this session.
        /// </summary>
        public IList<Page> Pages
        {
            get 
            {
                IList<Page> results = new List<Page>();
                foreach (KeyValuePair<Guid, Page> pair in m_mapPageIDsToPages)
                {
                    results.Add(pair.Value);
                }
                return results;
            }
        }

        /// <summary>
        /// Property to get or set the date and time that the session was created.
        /// </summary>
        public DateTime CreationDateTime
        {
            get { return m_CreationDateTime; }
            set { m_CreationDateTime = value; }
        }

        /// <summary>
        /// Property to get or set the name / title of the session.
        /// </summary>
        public string Name
        {
            get { return m_strName; }
            set { m_strName = value; }
        }

        /// <summary>
        /// Property to get or set a user-defined tag for the session.
        /// </summary>
        public string Tag
        {
            get { return m_strTag; }
            set { m_strTag = value; }
        }

        #endregion

        #region Public methods

        /// <summary>
        /// Singleton's getInstance method.
        /// </summary>
        public static Session getInstance()
        {
            if (m_Instance == null)
            {
                m_Instance = new Session();
            }
            return m_Instance;
        }

        /// <summary>
        /// Clears the existing session and creates a new one.
        /// </summary>
        public void newSession(ActionOrigin actionOrigin)
        {
            newSession(Guid.NewGuid(), actionOrigin);
        }

        /// <summary>
        /// Clears the existing session and creates a new one.
        /// </summary>
        public void newSession(Guid sessionID, ActionOrigin actionOrigin)
        {
            // We clear the existing session...
            m_mapPageIDsToPages.Clear();
            setActivePage(null, actionOrigin);

            // We set the ID...
            m_ID = sessionID;

            // We set the creation time...
            CreationDateTime = DateTime.Now;

            // We raise an event to tell observers that there is a new session...
            Utility.raiseEvent(NewSessionEvent, this, new Args(null, null, actionOrigin));
        }

        /// <summary>
        /// Adds a page to the session and sets it as the active page.
        /// </summary>
        public Page addPage(ActionOrigin actionOrigin)
        {
            Page page = new Page();
            setupNewPage(page, actionOrigin);
            return page;
        }

        /// <summary>
        /// Adds a page with the passed in page ID and sets it as the active page.
        /// </summary>
        public Page addPage(Guid pageID, ActionOrigin actionOrigin)
        {
            Page page = new Page(pageID);
            setupNewPage(page, actionOrigin);
            return page;
        }

        /// <summary>
        /// Returns the page with the ID passed in.
        /// Throws an exception if we do not have the page in the session.
        /// </summary>
        public Page getPage(Guid pageID)
        {
            if (m_mapPageIDsToPages.ContainsKey(pageID) == false)
            {
                throw new Exception("Session does not contain page with ID=" + pageID.ToString());
            }
            return m_mapPageIDsToPages[pageID];
        }

        /// <summary>
        /// Returns the active page.
        /// </summary>
        public Page getActivePage()
        {
            return m_ActivePage;
        }

        /// <summary>
        /// Sets the active page. 
        /// </summary>
        public void setActivePage(Page activePage, ActionOrigin actionOrigin)
        {
            // We check that the page passed in is one that we are managing...
            if (activePage != null)
            {
                bool bGotPage = m_mapPageIDsToPages.ContainsKey(activePage.ID);
                if (bGotPage == false)
                {
                    throw new Exception("Session does not contain the page passed to ActivePage.");
                }
            }
            m_ActivePage = activePage;
            Utility.raiseEvent(ActivePageChangedEvent, this, new Args(activePage, null, actionOrigin));
        }

        /// <summary>
        /// Returns the ID of the active page.
        /// </summary>
        public Guid getActivePageID()
        {
            return m_ActivePage.ID;
        }

        /// <summary>
        /// Sets the page with the ID passed in as the active page.
        /// </summary>
        public void setActivePageID(Guid activePageID, ActionOrigin actionOrigin)
        {
            bool bGotPage = m_mapPageIDsToPages.ContainsKey(activePageID);
            if (bGotPage == false)
            {
                throw new Exception("Page with ID=" + activePageID.ToString() + " is not part of the current session.");
            }
            Page activePage = m_mapPageIDsToPages[activePageID];
            setActivePage(activePage, actionOrigin);
        }

        #endregion

        #region Private functions

        /// <summary>
        /// Private constructor (for singleton).
        /// </summary>
        private Session()
        {
            m_ID = Guid.NewGuid();
            CreationDateTime = DateTime.Now;
        }

        /// <summary>
        /// We set up a new page, adding it to our session, observing its events etc.
        /// </summary>
        private void setupNewPage(Page page, ActionOrigin actionOrigin)
        {
            page.SessionID = m_ID;

            // We observe shape-edit-completed events (so that we can pass
            // them to our observers, such as the networking client)...
            page.ShapeEditCompletedEvent += onShapeEditCompleted;
            page.ShapeAddedEvent += onShapeAdded;
            page.ShapeRemovedEvent += onShapeRemoved;

            // We add it to our collection, and raise an event...
            m_mapPageIDsToPages[page.ID] = page;
            Utility.raiseEvent(PageAddedEvent, this, new Args(page, null, actionOrigin));

            // and we set it as the active page...
            setActivePage(page, actionOrigin);
        }

        /// <summary>
        /// Called when a shape has been removed from one of the pages
        /// in the session.
        /// </summary>
        private void onShapeRemoved(object sender, Page.Args e)
        {
            // We raise our shape-removed event to our observers...
            Page page = (Page)sender;
            Args args = new Args(page, e.Shape, e.ActionOrigin);
            Utility.raiseEvent(ShapeRemovedEvent, this, args);
        }

        /// <summary>
        /// Called when a shape has been added to one of the pages in the session.
        /// </summary>
        private void onShapeAdded(object sender, Page.Args e)
        {
            // We raise our page-added event...
            Page page = (Page)sender;
            Args args = new Args(page, e.Shape, e.ActionOrigin);
            Utility.raiseEvent(ShapeAddedEvent, this, args);
        }

        /// <summary>
        /// Called when a shape has been edited in one of the pages in the
        /// session.
        /// </summary>
        void onShapeEditCompleted(object sender, Page.Args e)
        {
            // We raise an event to our observers to let them know that the
            // shape has been edited, and which page it is on...
            Page page = (Page)sender;
            Args args = new Args(page, e.Shape, e.ActionOrigin);
            Utility.raiseEvent(ShapeEditCompletedEvent, this, args);
        }

        #endregion

        #region Private data

        // Static singleton instance...
        private static Session m_Instance = null;

        // Unique ID of the session...
        private Guid m_ID;

        // The collection of pages in this session...
        private IDictionary<Guid, Page> m_mapPageIDsToPages = new Dictionary<Guid, Page>();

        // The active page...
        private Page m_ActivePage = null;

        // Some members that help identify the session in the DB and to the user...
        private DateTime m_CreationDateTime;
        private string m_strName = "";
        private string m_strTag = "";

        #endregion
    }
}
